package groupManagementModule;

import generalObjects.BaseClientContract;
import generalObjects.GroupView;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.ServerNotActiveException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

import communicationModule.MessageObject;
import communicationModule.SendResult;

/**
 * This class interprets incoming messages and acts according to the type of
 * message received. These are for example election, message or update messages.
 * Identified by a message header.
 * 
 */
public class MessageHandler {

    private final GroupView gView;
    private final String myNick;
    private final Properties sessionProp;
    private final BullyAlgorithm bully;
    private final AtomicBoolean groupChanged;

    public MessageHandler(final GroupView gView, final Properties sessionProp,
            String[] nameservers, AtomicBoolean groupChanged) {
        this.gView = gView;
        myNick = sessionProp.getProperty("nickname");
        this.sessionProp = sessionProp;
        this.groupChanged = groupChanged;
        bully = new BullyAlgorithm(sessionProp, nameservers, groupChanged);
    }

    public List<SendResult> handleResult(final List<SendResult> results)
            throws RemoteException, NotBoundException, ServerNotActiveException {

        final List<SendResult> messagesForGUI = new ArrayList<SendResult>();
        String header;

        for (final SendResult result : results) {

            header = result.getMsg().getHeader();

            if (header.equals("message")) {
                messagesForGUI.add(result);
                if (myNick.equals(gView.getLeader())) {
                    for (String nick : gView.getView().keySet()) {
                        // TODO check if the client is in badreceivers as well.
                        MessageObject msg = result.getMsg();
                        if (!msg.getGroupView().getView().containsKey(nick)) {
                            BaseClientContract stub = gView.getView().get(
                                    myNick);
                            stub.send(new MessageObject(myNick, "sync", msg
                                    .getSender(), gView, nick));
                        }
                    }
                }
            } else if (header.equals("sync")) {
                MessageObject msg = result.getMsg();
                String syncName = msg.getContent();

                if (myNick.equals(msg.getSystemData())) {
                    int myseq = gView.getVectorClock().get(syncName);
                    int seq = msg.getGroupView().getVectorClock().get(syncName);
                    if (myseq < seq) {
                        gView.getVectorClock().put(syncName, seq);
                    }
                }
            } else if (header.equals("update")) {
                if (!gView.getView().containsKey(result.getMsg().getContent())) {
                    final String nickName = result.getMsg().getContent();
                    final int seq = result.getMsg().getGroupView()
                            .getVectorClock().get(nickName);
                    final BaseClientContract stub = result.getMsg()
                            .getGroupView().getView().get(nickName);
                    gView.appendMember(nickName, stub, seq);
                    groupChanged.compareAndSet(false, true);
                } else if (result.getMsg().getContent()
                        .equals(sessionProp.getProperty("nickname"))) {
                    // my own update message.
                    GroupView leadervector = gView.getView()
                            .get(gView.getLeader()).getVectorClockBaseline();
                    for (String nick : gView.getView().keySet()) {
                        // update my vectorclock to this baseline.
                        if (leadervector.getView().get(nick) != null) {
                            if (!nick.equals(myNick)) {
                                final int seq = leadervector.getVectorClock()
                                        .get(nick);
                                final BaseClientContract stub = leadervector
                                        .getView().get(nick);
                                gView.appendMember(nick, stub, seq);
                                groupChanged.compareAndSet(false, true);
                            }
                        } else {
                            gView.removeMember(nick);
                            groupChanged.compareAndSet(false, true);
                        }
                    }
                }
            } else if (header.equals("election")) {
                if (result.getMsg().getSender().equals(myNick)) {
                    // if the client receives that all clients with higher
                    // identifiers were crashed
                    if (amILeader(result)) {
                        bully.sendCoordinatorMsg(gView, myNick, result.getMsg()
                                .getContent());
                    }
                } else {
                    bully.startBully(gView, myNick, result.getMsg()
                            .getContent());
                }
            } else if (header.equals("coordinator")) {
                String newLeader = result.getMsg().getContent();
                String oldLeader = gView.getLeader();
                /*
                 * if multiple coord. msgs. arrives trying to replace the same
                 * leader, it will be blocked.
                 */
                if (!newLeader.equals(oldLeader)) {
                    gView.removeMember(oldLeader);
                    groupChanged.compareAndSet(false, true);
                    gView.setLeader(newLeader);
                }
            }
            if (hasLeaderCrashed(result)) {
                bully.startBully(gView, myNick, gView.getLeader());
            }
            removeNonLeaderCrashedClientsFromView(result);
        }

        return (!messagesForGUI.isEmpty() ? messagesForGUI : null);

    }

    private void removeNonLeaderCrashedClientsFromView(final SendResult result) {
        if (result.getBadRecvrs().size() > 0) {

            for (int i = 0; i < result.getBadRecvrs().size(); i++) {
                if (!result.getBadRecvrs().get(i).equals(gView.getLeader())) {
                    gView.removeMember(result.getBadRecvrs().get(i));
                    groupChanged.compareAndSet(false, true);
                }
            }
        }
    }

    private boolean hasLeaderCrashed(final SendResult result) {
        if (result.getBadRecvrs().size() > 0) {
            for (int i = 0; i < result.getBadRecvrs().size(); i++) {
                if (result.getBadRecvrs().get(i).equals(gView.getLeader())) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean amILeader(final SendResult result) {
        if ((result.getMsg().getGroupView().getView().keySet().size() - 1) == result
                .getBadRecvrs().size()) {
            for (int h = 0; h < result.getBadRecvrs().size(); h++) {
                result.getMsg().getGroupView().getView()
                        .remove(result.getBadRecvrs().get(h));
            }
            if (result.getMsg().getGroupView().getView().get(myNick) != null) {
                return true;
            }
        }
        return false;
    }
}
